<?php
class TreeVisitor implements IVisitor {
	
	/**
	 * The GeryCompileEngine
	 *
	 * @var GeryCompileEngine
	 */
	private $compileEngine;
	
	private $foreachCount;
	
	/**
	 * The code generator
	 *
	 * @var TemplateCodeGenerator
	 */
	private $generator;
	
	public function __construct($compileEngine, $generator) {
		
		$this->compileEngine = $compileEngine;
		$this->foreachCount = 0;
		
		$this->generator = $generator;
	}
	
	public function visitBlock(IASTNode $node, $prevIsDirective = true) {
		
		// merge succeeding smurk nodes
		$smurkBuffer = null;
		$nodes = array();
		
		foreach ($node->nodes as $node) {
			if ($node instanceof ASTSmurk) {
				if ($smurkBuffer) {
					$smurkBuffer->smurk .= $node->smurk;
				} else {
					$smurkBuffer = $node;
				}
			} else {
				if ($smurkBuffer !== null) {
					$nodes[] = $smurkBuffer;
					$smurkBuffer = null;
				}
					
				$nodes[] = $node;
			}
		}
		
		// empty the smurkbuffer
		if ($smurkBuffer !== null)
			$nodes[] = $smurkBuffer;
		
		// and emit code for all nodes
		foreach($nodes as $subNode) 
		{
			// new lines from smurk nodes after directive
			if ($subNode instanceof ASTSmurk && $prevIsDirective) {
				if (preg_match("/^(\s+)/", $subNode->smurk, $match)) {
					$subNode->smurk = substr($subNode->smurk, strlen($match[1]));
				}
			}
			
			// let the compiler accept this subnode
			$subNode->accept($this);
			
			$prevIsDirective = ($subNode instanceof IASTDirective);
		}
	}
	
	public function visitElseDirective(ASTElseDirective $node) {
		$this->generator->appendElse();
		$node->block->accept($this);
	}
	
	public function visitElseIfDirective(IASTNode $node) {
		// compile condition
		$exprVisitor = new ExpressionVisitor($this->compileEngine);
		$node->condition->accept($exprVisitor);
		
		$this->generator->appendElse($exprVisitor->getOutput());
		
		$node->trueBlock->accept($this);
		$node->falseBlock->accept($this);
	}
	
	public function visitForeachDirective(ASTForeachDirective $node) {
		
		$this->foreachCount++;
		
		$keyVar = '$foreachKey_' . $this->foreachCount;
		$arrayVar = '$foreachArray_' . $this->foreachCount;
		
		// get array
		$exprVisitor = new ExpressionVisitor($this->compileEngine);
		$node->array->accept($exprVisitor);
		
		// get context key
		$key = $node->key->name;
		
		
		$this->generator->appendCustomCode("{$arrayVar} = " . $exprVisitor->getOutput() . ';');
		
		// check if the array is iterable
		$this->generator->appendIf("! is_array(" . $arrayVar . ") && ! " . $arrayVar . " instanceof IGeryArray");
		$this->generator->appendCustomCode("TemplateError::foreachParameterNotArray({$node->line}, \$context);");
		$this->generator->appendEndIf();
		
		$this->generator->appendIf("(is_array(" . $arrayVar . ") || " . $arrayVar . " instanceof IGeryArray) && count(" . $arrayVar . ")");
		$this->generator->appendForeach($arrayVar . " as " . $keyVar);
		$this->generator->appendCustomCode("\$context->put('" . $key . "', " . $keyVar . ");");
		
		// loop block
		$node->loopBlock->accept($this);
		
		// end foreach
		$this->generator->appendEndForeach();
		
		if ($node->else !== null)
			$node->else->accept($this);
			
		$this->generator->appendEndIf();
		
		$this->foreachCount--;
	}
	
	public function visitIfDirective(ASTIfDirective $node) 
	{
		// compile the if condition
		$exprVisitor = new ExpressionVisitor($this->compileEngine);
		$node->condition->accept($exprVisitor);
		
		$this->generator->appendIf($exprVisitor->getOutput());
		
		$node->trueBlock->accept($this);
		
		if ($node->falseBlock !== null)
			$node->falseBlock->accept($this);
		
		$this->generator->appendEndIf();
	}
	
	public function visitInterpolation(IASTNode $node) {
		$exprVisitor = new ExpressionVisitor($this->compileEngine);
		$node->expression->accept($exprVisitor);
		
		$this->generator->appendCustomCode('$output .= ' . $exprVisitor->getOutput() . ';');
	}
	
	public function visitMacro(IASTNode $node) {
		
		$this->compileEngine->registerMacro($node->name);
		
		// create parameter string
		$parameters = array();
		foreach ($node->parameters as $parameter) {
			$parameters[] = "'" . $parameter->name . "'";
		}
		$parameters = 'array(' . implode(",", $parameters) . ')';
		
		// get block name, and enter macro scope
		$macroName = strtolower($node->name);
		
		$this->generator->startMacro($macroName);
		$this->generator->appendCustomCode("function __macro_{$macroName}(IGeryContext \$outerContext, \$args) {", CodeGenerator::STYLE_INDENT);
		$this->generator->appendCustomCode("\$output = '';");
		$this->generator->appendCustomCode("\$context = new GeryMacroContext(\$outerContext, '$macroName');");
		$this->generator->appendCustomCode("\$context->put('parameters', $parameters);");
		$this->generator->appendForeach("{$parameters} as \$i => \$parameter");
		$this->generator->appendCustomCode("\$context->put(\$parameter, (key_exists(\$i, \$args)) ? \$args[\$i] : null);");
		$this->generator->appendEndForeach();
		
		$node->block->accept($this);
		
		$this->generator->appendCustomCode('return $output;');
		$this->generator->appendCustomCode('}', ICodeGenerator::STYLE_DEDENT);
		
		$this->generator->endMacro();
	}
	
	public function visitMacroCall(ASTMacroCall $node) {
		
		// compile parameters
		$parameters = array();
		
		foreach ($node->parameters as $parameter) {
			
			if ($parameter instanceof ASTReference) {
				$reference = TemplateCodeGenerator::createReference($parameter, $this->compileEngine);
				
				$parameters[] = 'new ReferenceProxy(' . $reference . ', $context)';
			} else {
				$compiler = new ExpressionVisitor($this->compileEngine);
				$parameter->accept($compiler);
				$parameters[] = $compiler->getOutput();
			}
		}
		
		$macroName = strtolower($node->name);
		
		// create array code
		$parameters = 'array(' . implode(",", $parameters) . ')';
		
		$this->generator->appendIf("\$context->isMacroRegistered('$macroName')");
		$this->generator->appendValueAssignment('output', str_repeat("\\", $node->escapes));
		$this->generator->appendCustomCode("\$output .= \$context->callMacro('$macroName', $parameters);\n");
		$this->generator->appendElse();
		$this->generator->appendValueAssignment('output', $node->smurk, ICodeGenerator::ASSIGN_APPEND_TEXT);
		$this->generator->appendEndIf();
	}
	
	public function visitParseDirective(ASTParseDirective $node) 
	{
		$exprCompiler = new ExpressionVisitor($this->compileEngine);
		$node->resource->accept($exprCompiler);
		
		$templateResource = $exprCompiler->getOutput();
		
		$this->generator->appendCustomCode('$output .= $this->parseDirective(' . $templateResource . ', $context, ' . $node->line . ');');
	}
	
	public function visitIncludeDirective(ASTIncludeDirective $node) {
		
		$exprCompiler = new ExpressionVisitor($this->compileEngine);
		$node->resource->accept($exprCompiler);
		
		$templateResource = $exprCompiler->getOutput();
		
		$this->generator->appendCustomCode('$output .= $this->includeDirective(' . $templateResource . ', $context, ' . $node->line . ');');
	}
	
	public function visitReference(IASTNode $node) {
		
		// odd number of slashes
		if ($node->escapes % 2 == 1) {
			$slashes = str_repeat('\\', ($node->escapes - 1));
			
			$this->generator->appendValueAssignment('output', str_repeat('\\', ($node->escapes - 1) / 2) . $node->smurk, CodeGenerator::ASSIGN_APPEND_TEXT);
		} 
		
		// even number of slashes
		else 
		{
			if ($node->escapes)
				$this->generator->appendValueAssignment('output', str_repeat('\\', $node->escapes / 2), CodeGenerator::ASSIGN_APPEND_TEXT);
				
			$reference = TemplateCodeGenerator::createReference($node, $this->compileEngine);
			
			$this->generator->appendCustomCode('$output .= TemplateBase::getReference($context, ' . $reference . ', true);');			
		}
	}
	
	public function visitSetDirective(IASTNode $node) {
		
		$reference = TemplateCodeGenerator::createReference($node->reference, $this->compileEngine);
		$exprCompiler = new ExpressionVisitor($this->compileEngine);
		$node->expression->accept($exprCompiler);
		
		$this->generator->appendCustomCode('TemplateBase::setReference($context, ' . $reference . ', ' . $exprCompiler->getOutput() . ');');
		
	}
	
	public function visitSmurk(IASTNode $node) {
		if ($node->smurk !== false)
			$this->generator->appendValueAssignment('output', $node->smurk, CodeGenerator::ASSIGN_APPEND_TEXT);
	}
	
	public function visitStopDirective(ASTStopDirective $node) {
		$this->generator->appendCustomCode('return;');
	}
}

class ExpressionVisitor implements IVisitor {
	
	private $compileEngine;
	private $output;
	
	function ExpressionVisitor($compileEngine) {
		$this->output = '';	
	}
	
	function visitAdd(IASTNode $node) {
		
		$a = new ExpressionVisitor($this->compileEngine);
		$node->left->accept($a);
		
		$b = new ExpressionVisitor($this->compileEngine);
		$node->right->accept($b);
		
		$this->output .= 'TemplateMath::add(' . $a->getOutput() . ', ' . $b->getOutput() . ')';
	}
	
	function visitArray(IASTNode $node) {
		
		if ($node->elements instanceof ASTArrayRange) {
			$range = $node->elements;
			
			$lowCompiler = new ExpressionVisitor($this->compileEngine);
			$range->low->accept($lowCompiler);
			
			$highCompiler = new ExpressionVisitor($this->compileEngine);
			$range->high->accept($highCompiler);
			
			$this->output = "TemplateMath::arrayRange(" . $lowCompiler->getOutput() 
				. ", " . $highCompiler->getOutput() . ")";
			
		} else {
			$this->output .= 'array(';
			
			$output = array();
			foreach ($node->elements as $element) {
				$compiler = new ExpressionVisitor($this->compileEngine);
				$element->accept($compiler);
				$output[] = $compiler->getOutput();
			}
			
			$this->output .= implode(', ', $output);
			$this->output .= ')';	
		}
	}
	
	function visitDivide(IASTNode $node) {
		$a = new ExpressionVisitor($this->compileEngine);
		$node->left->accept($a);
		
		$b = new ExpressionVisitor($this->compileEngine);
		$node->right->accept($b);
		
		$this->output .= 'TemplateMath::divide(' . $a->getOutput() . ', ' . $b->getOutput() . ')';
	}
	
	function visitLiteral(IASTNode $node) {
		$this->output .= '\''. $node->value . '\'';
	}
	
	function visitModulus(IASTNode $node) {
		
		$a = new ExpressionVisitor($this->compileEngine);
		$node->left->accept($a);
		
		$b = new ExpressionVisitor($this->compileEngine);
		$node->right->accept($b);
		
		$this->output .= 'TemplateMath::modulus(' . $a->getOutput() . ', ' . $b->getOutput() . ')';
	}
	
	function visitMultiply(IASTNode $node) {
		
		$a = new ExpressionVisitor($this->compileEngine);
		$node->left->accept($a);
		
		$b = new ExpressionVisitor($this->compileEngine);
		$node->right->accept($b);
		
		$this->output .= 'TemplateMath::multiply(' . $a->getOutput() . ', ' . $b->getOutput() . ')';
	}
	
	function visitNumber(IASTNode $node) {
		$this->output .= $node->value;
	}
	
	function visitScalarObject(IASTNode $node) {
		$a = new ExpressionVisitor($this->compileEngine);
		$node->scalar->accept($a);
		
		$modifiers = TemplateCodeGenerator::createModifiers($node->modifiers, $this->compileEngine);
		$this->output = "TemplateBase::parseModifiers({$a->getOutput()}, $modifiers)";
	}
	
	function visitParenthesis(IASTNode $node) {
		$a = new ExpressionVisitor($this->compileEngine);
		$node->expr->accept($a);
		
		$this->output .= '(' . $a->getOutput() . ')';
	}
	
	function visitReference(IASTnode $node) {
		$reference = TemplateCodeGenerator::createReference($node, $this->compileEngine);
		$this->output = 'TemplateBase::getReference($context, ' . $reference . ', false)';
	}
	
	function visitSub(IASTNode $node) {
		
		$a = new ExpressionVisitor($this->compileEngine);
		$node->left->accept($a);
		
		$b = new ExpressionVisitor($this->compileEngine);
		$node->right->accept($b);
		
		$this->output .= 'TemplateMath::substract(' . $a->getOutput() . ', ' . $b->getOutput() . ')';
	}
	
	/**
	 * Compare methods
	 */
	function visitEqual(IASTNode $node) {
		$a = new ExpressionVisitor($this->compileEngine);
		$node->left->accept($a);
		
		$b = new ExpressionVisitor($this->compileEngine);
		$node->right->accept($b);
		
		$this->output .= '(' . $a->getOutput() . ' == ' . $b->getOutput() . ')';
	}
	
	function visitNotEqual(IASTNode $node) {
		$a = new ExpressionVisitor($this->compileEngine);
		$node->left->accept($a);
		
		$b = new ExpressionVisitor($this->compileEngine);
		$node->right->accept($b);
		
		$this->output .= $a->getOutput() . ' != ' . $b->getOutput();	
	}
	
	function visitNot(ASTNot $node) {
		$this->output .= '! ';
		
		$a = new ExpressionVisitor($this->compileEngine);
		$node->expr->accept($a);
		
		$this->output .= $a->getOutput();	
	}
	
	function visitGreaterThan(IASTNode $node) {
		$left = new ExpressionVisitor($this->compileEngine);
		$node->left->accept($left);
		
		$right = new ExpressionVisitor($this->compileEngine);
		$node->right->accept($right);
		
		$this->output .= $left->getOutput() . " > " . $right->getOutput();
	}
	
	function visitGreaterOrEqual(IASTNode $node) {
		$left = new ExpressionVisitor($this->compileEngine);
		$node->left->accept($left);
		
		$right = new ExpressionVisitor($this->compileEngine);
		$node->right->accept($right);
		
		$this->output .= $left->getOutput() . " >= " . $right->getOutput();
	}
	
	function visitLessThan(IASTNode $node) {
		$left = new ExpressionVisitor($this->compileEngine);
		$node->left->accept($left);
		
		$right = new ExpressionVisitor($this->compileEngine);
		$node->right->accept($right);
		
		$this->output .= $left->getOutput() . " < " . $right->getOutput();
	}
	
	function visitLessOrEqual(IASTNode $node) {
		$left = new ExpressionVisitor($this->compileEngine);
		$node->left->accept($left);
		
		$right = new ExpressionVisitor($this->compileEngine);
		$node->right->accept($right);
		
		$this->output .= $left->getOutput() . " <= " . $right->getOutput();
	}
	
	function visitLogicalAnd(IASTNode $node) {
		$left = new ExpressionVisitor($this->compileEngine);
		$node->left->accept($left);
		
		$right = new ExpressionVisitor($this->compileEngine);
		$node->right->accept($right);
		
		$this->output .= $left->getOutput() . " && " . $right->getOutput();
	}
	
	function visitLogicalOr(IASTNode $node) {
		$left = new ExpressionVisitor($this->compileEngine);
		$node->left->accept($left);
		
		$right = new ExpressionVisitor($this->compileEngine);
		$node->right->accept($right);
		
		$this->output .= $left->getOutput() . " || " . $right->getOutput();
	}
	
	function visitMacroCall(ASTMacroCall $node) {
		
		// compile parameters
		$parameters = array();
		
		foreach ($node->parameters as $parameter) {
			$compiler = new ExpressionVisitor($this->compileEngine);
			$parameter->accept($compiler);
			$parameters[] = $compiler->getOutput();
		}
		
		$macroName = $node->name;
		
		// create array code
		$parameters = 'array(' . implode(",", $parameters) . ')';
		
		$this->output .= "\$context->callMacro('$macroName', $parameters)";
	}
	
	function getOutput() {
		return $this->output;
	}
}
?>